package SSF.OS.TCP;
/**
 * Rcv_WndManager.java
 *
 * Created: Mon Jul 19 02:24:52 1999 hongbol
 * Modified Sat Nov 20 1999 ato
 * Modified by hongbol 4/16/2000
 * Modified by hongbol 8/16/2000
 * @version 0.9.0
 */
      
import SSF.OS.*;
import SSF.OS.Socket.*;
/**
 * Receive window manager keeps a record of the receive window parameters
 * and change their value accordingly when it receives a new message and
 * sends an ACK. It maintains the receive buffer and keeps all the messages 
 * in order before they are read by upper layer Application.
 */ 
public class Rcv_WndManager
{ 
  /** 
   * Reference to the owner of this receive window manager
   */
  protected tcpSession owner;
   
  /**
   * Sequence number of the next expected packet to receive ( = ACK number).
   */
  protected long NextExpected;
  /**
   * Sequence number of the last packet read by application
   */
  protected long LastRead;
  /**
   * Sequence number of the last received packet
   */
  protected long LastRcvd;
  /**
   * Index of the first out-of-sequence packet in the Receive Buffer
   */ 
  protected int GapPosition;
  /**
   * Size of the advertised window
   */
  public int AdvertisedWnd;

  /* max advised sequence number */
  public long maxAdvertised;

  /**
   * Default maximum receive window size
   */
  public int MAX_RCV_WND;
  /**
   * Maximum number of packets in receive buffer
   */
  public static final int SIZE = 64;
  
  public int MSS;

  long lastExp;

  /** template of the reply message*/
  protected TCP_Header tcpHdr;

  /** 
   * Receive buffer for the received messages, including out of order messages.
   */
  protected TCP_Message[] Rcv_Buffer;

  /**
   * index to the receive buffer. Filled buffer is [LowerBound, UpperBound)
   */
  private int LowerBound;  // not used, always equal to GapPosition
  private int UpperBound;  // next free slot to receive a message

  private int Incompletepkt;
  private int Index;

  // indicate the receipt of message with FIN flag
  boolean FINreceived = false;
  boolean ACKFINreceived = false;

  boolean DELACK_ON;
  /**
   * Initialize receive window parameter and receive buffer space.
   * @param tcp a reference to the tcpSession class managing this object
   */
  Rcv_WndManager(tcpSession tcp, tcpSessionMaster init) {
    owner = tcp;
    MSS = init.MSS;
    MAX_RCV_WND = init.RcvWndSize * MSS;
    DELACK_ON = ((init.tcp_opt & init.DELACK) != 0);
    NextExpected = 1;
    LastRead = 0;
    LastRcvd = 0;
    Rcv_Buffer = new TCP_Message[SIZE];
    GapPosition = 0;
    LowerBound = UpperBound = 0;
    tcpHdr = new TCP_Header();
  }

  String getRcvWndParam(){
    return (NextExpected + " " + LastRead +  " " + LastRcvd +
            " " + AdvertisedWnd);
  }

  /**
   * Calculate the size of the advertised window which indicate the maximum
   * number of bytes that can be received in the buffer.
   */
  public void Calc_AdvertisedWindow(){

    //AdvertisedWnd = MAX_RCV_WND - (int) (LastRcvd - NextExpected + 1);
    AdvertisedWnd = MAX_RCV_WND - (int) (NextExpected - 1 - LastRead);
  }

  /* OBSOLETED ato 1/24/02
   * Test if the received sequence number of the message is out of range.
   *
  public boolean DataNeedDrop(TCP_Message msg, long nextexp){
    return (msg.SEQno < nextexp);
      
  }
  */
  
  /**
   * Sort the messages by sequence number, discard the message if
   * it's duplicated, increase the next expected sequence number if needed.
   * Push in-order (virtual) data segments to the reading socket buffer.
   */
  public void ReassembleMessage() {
    int endpos = (UpperBound-1 + SIZE) % SIZE; // newest TCP message position

    if (endpos != GapPosition) {  // there is a gap in sequence before the newest
      long endSEQno = Rcv_Buffer[endpos].SEQno;

      // find insert position index
      int index = (endpos-1 + SIZE) % SIZE;
      while ((index != GapPosition) &&
             (Rcv_Buffer[index].SEQno > endSEQno)) {
        index = (index-1 + SIZE) % SIZE;
      }

      if (Rcv_Buffer[index].SEQno == endSEQno) {
        // newest TCP message is a duplicate, remove it
        UpperBound = endpos;
      } else {
        if (Rcv_Buffer[index].SEQno < endSEQno)
          index = (index+1) % SIZE;

        if (index != endpos) {
          TCP_Message holder = Rcv_Buffer[endpos];

          // shift elements from GapPosition up by one
          int prev;
          for (int cur=endpos; cur != index; cur=prev) {
            prev = (cur-1 + SIZE) % SIZE;
            Rcv_Buffer[cur] = Rcv_Buffer[prev];
          }
          Rcv_Buffer[index] = holder;
        }
      }
    }

    /* Messages are sorted by sequence number. Check NextExpected,
     * which is an element between GapPosition and UpperBound.
     * Move GapPosition up if the newest segment fills the (oldest) gap.
     */

    int prev = 0;
    while ((GapPosition != UpperBound) &&
           NextExpected == Rcv_Buffer[GapPosition].SEQno) {
      if ((Rcv_Buffer[GapPosition].flags & TCP_Header.FIN) != 0){
        FINreceived = true;
        NextExpected += 1;

        /* commented out ato 5/28/01
        if (((Rcv_Buffer[GapPosition].flags & TCP_Header.ACK) != 0) &&
            ((Rcv_Buffer[GapPosition].ACKno >= (owner.snd.FINSeq + 1))))
          ACKFINreceived = true;
        */
      }
      else if(Rcv_Buffer[GapPosition].payload() != null){
        // push previous virtual data to "socket buffer"
        push_Process();
        NextExpected += Rcv_Buffer[GapPosition].seglen;
      
        // save data object ref to socket buffer - it is important,
        // because upon receiving the ACK for this message
        // the sender may obliterate this TCP_Message.
        owner.dataCollect((dataMessage)Rcv_Buffer[GapPosition].payload(),
                          Rcv_Buffer[GapPosition].seglen);
        LastRead = NextExpected - 1;
      }
      else {
        NextExpected += Rcv_Buffer[GapPosition].seglen;
      }
      prev = GapPosition;
      GapPosition = (GapPosition+1) % SIZE;
    }
    LowerBound = GapPosition;
  }

  /**
   * Initialize the receive window according to message received. Set the
   * value of receive window variable and TCP header of the replying
   * message, send acknowledgment message in reply to the connection request.
   * Called by tcpSession.push() upon receiving SYN or SYN-ACK.
   */
  public void Rcv_WndInit(TCP_Message data ){
    switch(owner.tcp_state){
      case tcpSession.LISTEN:
        {
        LastRcvd = data.SEQno;
        NextExpected = data.SEQno+1;
        tcpHdr.SOURCE_port = data.DEST_port;
        LastRead = data.SEQno;
        tcpHdr.DEST_port = data.SOURCE_port;
        tcpHdr.SEQno = 0;
        tcpHdr.ACKno = NextExpected;
        tcpHdr.AdvertisedWnd = MAX_RCV_WND;
        tcpHdr.flags = (byte)(TCP_Header.SYN | TCP_Header.ACK);

        TCP_Header tcp_h = new TCP_Header(tcpHdr);
        TCP_Message msg = new TCP_Message(tcp_h, owner.ipHdr.SOURCE_IP,
                                          owner.ipHdr.DEST_IP, 0);
        owner.snd.SendBuffer.append(msg, 0, tcp_h, owner.ipHdr.SOURCE_IP,
                                    owner.ipHdr.DEST_IP, false);
        owner.snd.Send_Data_Process();
        }
        break;

      case tcpSession.SYN_SENT:
        {
        LastRcvd = data.SEQno;
        NextExpected = data.SEQno+1;
        LastRead = data.SEQno;
        owner.snd.AdvertisedWnd = data.AdvertisedWnd;
        tcpHdr.SEQno = owner.snd.LastSent + 1;
        tcpHdr.SOURCE_port = data.DEST_port;
        tcpHdr.DEST_port = data.SOURCE_port;
        tcpHdr.ACKno = NextExpected;
        tcpHdr.AdvertisedWnd = MAX_RCV_WND;
        tcpHdr.flags = TCP_Header.ACK;
	TCP_Header tcp_h = new TCP_Header(tcpHdr);
	TCP_Message message = new TCP_Message(tcp_h, owner.ipHdr.SOURCE_IP,
                                              owner.ipHdr.DEST_IP, 0);
	try {
	  owner.ipsess.push(message.ipHeader(),owner);
	} catch (ProtocolException e) {
	  System.err.println ("TCP: " + e);
	}
        }
        break;

      default:
        return;
    }
  }

  /**
   * Process receives a data message, sorts messages in sequence number order, sends
   * acknowledgment message with next expected sequence number and advertised 
   * window size, checks if the message contains flags indicating
   * the end of data transmission.
   * Note: dose not check if have a silly window syndrome.
   * @param message received TCP message
   */ 
  public void Rcv_DataProcess(ProtocolMessage message) throws TCPException {
    TCP_Message msg =  (TCP_Message)message;

    if(msg.SEQno < NextExpected) {  // test if outside valid receive sequence
      Send_Ack_Process();           // if yes, immediately send back ACK
    } else {
      if((UpperBound == GapPosition)&&(msg.SEQno == NextExpected) && DELACK_ON)
        owner.DELAYED_ACK = true;
      else {
        owner.DELAYED_ACK = false;
      }

      // Instert message in the next available slot of the receive buffer.

      Rcv_Buffer[UpperBound] = msg;
      UpperBound = (UpperBound + 1) % SIZE;
      if (UpperBound == LowerBound) {
	// ReceiveQueue holds SIZE segments, and will overflow
        throw (new TCPException(owner.sessionInfo("Receive Queue overflow")));
      }

      // Update highest so far received sequence number
      if((msg.SEQno + msg.seglen - 1) > LastRcvd) 
        LastRcvd = msg.SEQno + msg.seglen - 1;

      owner.ACK_NOW = true; // ack pending here

      ReassembleMessage();  // will set FINreceived if flag = FIN


      if(FINreceived){
        owner.CloseStateTransfer1();
      }

      if((lastExp + 2 * MSS) <= NextExpected){ // ack every 2 full-sized packets
        owner.ACK_NOW = true;
        owner.DELAYED_ACK = false;
      }

      Calc_AdvertisedWindow();

      // if receive buffer has valid virtual data, read to socket buffer.
      push_Process();

      if((!owner.DELAYED_ACK)&& owner.ACK_NOW){
	Send_Ack_Process();
      }
    }
  }

  /**
   * Send ACK for the received in-sequence packet with the highest sequence
   * number.
   */
  public void  Send_Ack_Process(){
    Calc_AdvertisedWindow();
    TCP_Header tcp_h = new TCP_Header(tcpHdr);
    tcp_h.SEQno = owner.snd.LastSent + 1;
    tcp_h.ACKno = NextExpected ;
    tcp_h.AdvertisedWnd = AdvertisedWnd;
    tcp_h.flags = TCP_Header.ACK;
    TCP_Message ACKmsg = new TCP_Message(tcp_h, owner.ipHdr.SOURCE_IP,
                                         owner.ipHdr.DEST_IP, 0);
    try {
      owner.set_protocol_status(3);// set write status
      owner.ipsess.push(ACKmsg.ipHeader(),owner);
      
    } catch (ProtocolException e) {
      System.err.println("TCP" + e);
    }
    lastExp = NextExpected;
    owner.snd.Send_Data_Process();
  }

  public void setACK(TCP_Message tcpMsg){
    if((tcpMsg.flags & TCP_Header.SYN) == 0) {

      tcpMsg.flags |= TCP_Header.ACK;   // TCP always sets ACK except for SYN

      tcpMsg.ACKno = NextExpected;
      Calc_AdvertisedWindow();
      tcpMsg.AdvertisedWnd = AdvertisedWnd;
      owner.DELAYED_ACK = false;
      owner.ACK_NOW = false;
      lastExp = tcpMsg.ACKno;
    }
  }

  /**
   * push all received and ordered virtual data to upper layer protocol session
   */
  public void push_Process(){
      int size = (int)(NextExpected - LastRead - 1);
      if(FINreceived)
        size -= 1;
      if(size > 0){
        owner.dataCollect(null,size);
        LastRead = NextExpected - 1;
      }
  }

}





